<?php

    class XmlParser
    {
        private $xmlParser = null;
        private $stack = array();

        /**
         * @var XmlNodeRoot
         */
        private $curNode;

        public function __construct()
        {
            $xmlParser = xml_parser_create( 'UTF-8' );

            xml_set_object($xmlParser, $this);
            xml_parser_set_option($xmlParser, XML_OPTION_CASE_FOLDING, false);

            xml_set_element_handler($xmlParser, 'startNode', 'endNode');
            xml_set_character_data_handler($xmlParser, 'nodeData');

            $this->xmlParser = $xmlParser;
        }

        /**
         * Parses xml and returns root node.
         *
         * @param string $xml
         * @return XmlNodeRoot
         */
        public function parse($xml)
        {
            $this->stack = array();

            $rootNode = new XmlNodeRoot('root', array());

            $this->stack[] = $rootNode;
            $this->curNode = $rootNode;

            if (!xml_parse($this->xmlParser, $xml))
            {
                return null;
            }

            return $rootNode;
        }

        /**
         * Handles node start.
         *
         * @param resource $xmlParser
         * @param string $name
         * @param array $attrs
         */
        private function startNode($xmlParser, $name, $attrs)
        {
            $node = new XmlNodeRoot($name, $attrs);
            $this->curNode->addChild($node);

            $this->stack[] = $node;
            $this->curNode = $node;
        }

        /**
         * Handles node end.
         *
         * @param resource $xmlParser
         * @param string $name
         */
        private function endNode($xmlParser, $name)
        {
            array_pop($this->stack);
            $this->curNode = $this->stack[count($this->stack) - 1];
        }

        /**
         * Handles node data.
         *
         * @param resource $xmlParser
         * @param string $data
         */
        private function nodeData($xmlParser, $data)
        {
            if (trim($data))
            {
                $this->curNode->setData($data);
            }
        }
    }

?>